package com.ruyuan.user.server.domain.service.impl;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Maps;
import com.ruyuan.common.model.BusinessException;
import com.ruyuan.common.model.ErrorCode;
import com.ruyuan.user.api.model.enums.UserJobRelationTypeEnum;
import com.ruyuan.user.server.domain.converter.JobConverter;
import com.ruyuan.user.server.domain.model.Job;
import com.ruyuan.user.server.domain.model.JobCacheWrapper;
import com.ruyuan.user.server.domain.model.Organization;
import com.ruyuan.user.server.domain.model.OrganizationCacheWrapper;
import com.ruyuan.user.server.domain.service.JobDomainService;
import com.ruyuan.user.server.domain.service.OrganizationDomainService;
import com.ruyuan.user.server.domain.service.request.DelJobReq;
import com.ruyuan.user.server.domain.service.request.SaveUserMultiJobReq;
import com.ruyuan.user.server.repository.bean.*;
import com.ruyuan.user.server.repository.mapper.JobMapper;
import com.ruyuan.user.server.repository.mapper.UserJobRelationMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author xx
 */
@Service
public class JobDomainServiceImpl implements JobDomainService {

    @Autowired
    private JobMapper jobMapper;

    @Autowired
    private OrganizationDomainService organizationDomainService;

    @Autowired
    private JobConverter jobConverter;

    @Autowired
    private UserJobRelationMapper userJobRelationMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    private static final String JOB_CACHE_PREFIX = "jobPrefix:";

    private static final Long LOCAL_REFRESH_TIME = 30L;

    private static final Long REDIS_REFRESH_TIME = 7L;

    private static final Long WRAPPER_INVALID_TIME = 5 * 60 * 1000L;

    private ExecutorService threadPool = new ThreadPoolExecutor(5, 10, 30L, TimeUnit.SECONDS, new LinkedBlockingDeque<>(Integer.MAX_VALUE));


    private LoadingCache<Long, Optional<Job>> jobCache = CacheBuilder.newBuilder()
            .expireAfterWrite(LOCAL_REFRESH_TIME, TimeUnit.SECONDS).build(
                    new CacheLoader<Long, Optional<Job>>() {
                        @Override
                        public Optional<Job> load(Long jobId) throws Exception {
                            BoundValueOperations<String, JobCacheWrapper> valueOps = redisTemplate.boundValueOps(JOB_CACHE_PREFIX + jobId);
                            JobCacheWrapper jobCacheWrapper = valueOps.get();
                            if (jobCacheWrapper == null) {
                                JobDO jobDO = jobMapper.selectByPrimaryKey(jobId);
                                jobCacheWrapper = new JobCacheWrapper();
                                jobCacheWrapper.setInvalidTime(System.currentTimeMillis() + WRAPPER_INVALID_TIME);
                                jobCacheWrapper.setJobDO(jobDO);
                                valueOps.set(jobCacheWrapper, REDIS_REFRESH_TIME, TimeUnit.MINUTES);
                            } else if (jobCacheWrapper.getInvalidTime() < System.currentTimeMillis()) {
                                threadPool.submit(() -> {
                                    String lockKey = JOB_CACHE_PREFIX + jobId + ":lock";
                                    Boolean lockResult = redisTemplate.boundValueOps(lockKey).setIfAbsent("1", 2L, TimeUnit.SECONDS);
                                    if (lockResult != null && lockResult) {
                                        //重新检查一遍是否已经被刷新成功
                                        JobCacheWrapper cacheAfterLock = valueOps.get();
                                        //已经被处理过就不处理了
                                        if (cacheAfterLock != null && cacheAfterLock.getInvalidTime() > System.currentTimeMillis()) {
                                            return;
                                        }
                                        JobDO jobDO = jobMapper.selectByPrimaryKey(jobId);
                                        JobCacheWrapper newWrapper = new JobCacheWrapper();
                                        newWrapper.setInvalidTime(System.currentTimeMillis() + WRAPPER_INVALID_TIME);
                                        newWrapper.setJobDO(jobDO);
                                        valueOps.set(newWrapper, REDIS_REFRESH_TIME, TimeUnit.MINUTES);
                                        // 释放锁
                                        redisTemplate.delete(lockKey);
                                    }
                                });
                            }
                            return Optional.ofNullable(jobConverter.convertDOToModel(jobCacheWrapper.getJobDO()));
                        }
                    }
            );

    @Override
    public Long saveJob(Job job) {
        checkParam(job);
        JobDO jobDO = jobConverter.convertModelToDO(job);
        if (jobDO.getId() == null) {
            jobMapper.insertSelective(jobDO);
        } else {
            jobMapper.updateByPrimaryKeySelective(jobDO);
        }
        redisTemplate.delete(JOB_CACHE_PREFIX + jobDO.getId());
        return jobDO.getId();
    }

    private void checkParam(Job job) {
        if (job.getOrgId() != null) {
            Organization organization = organizationDomainService.queryOrganizationById(job.getOrgId());
            if (organization == null) {
                throw new BusinessException(ErrorCode.code("ORG_NOT_EXIST", job.getOrgId()));
            }
        }
        if (job.getSuperiorJobId() != null) {
            Job superiorJob = queryById(job.getSuperiorJobId());
            if (superiorJob == null || superiorJob.getIsDeleted()) {
                throw new BusinessException(ErrorCode.code("JOB_NOT_EXIST", job.getSuperiorJobId()));
            }
        }
        //校验负责人岗位
        if (job.getIsBoss()) {
            List<Job> bossJobs = queryBossJobByOrgId(job.getOrgId());
            if (!CollectionUtils.isEmpty(bossJobs)) {
                throw new BusinessException(ErrorCode.code("ORG_ONLY_ONE_BOSS"));
            }
        }
    }

    @Override
    public Job queryById(Long id) {
        try {
            return jobCache.get(id).orElse(null);
        } catch (Exception e) {
            throw new BusinessException(ErrorCode.code("JOB_QUERY_ERROR"));
        }
    }

    @Override
    public List<Job> queryBossJobByOrgId(Long orgId) {
        JobExample jobExample = new JobExample();
        jobExample.createCriteria().andIsDeletedEqualTo("n").andIsBossEqualTo(true).andOrgIdEqualTo(orgId);
        List<JobDO> jobDOS = jobMapper.selectByExample(jobExample);
        if (CollectionUtils.isEmpty(jobDOS)) {
            return new ArrayList<>();
        } else {
            return jobDOS.stream().map(jobConverter::convertDOToModel).collect(Collectors.toList());
        }
    }

    @Override
    public List<Job> queryByOrgId(Long orgId) {
        JobExample jobExample = new JobExample();
        jobExample.createCriteria().andIsDeletedEqualTo("n").andOrgIdEqualTo(orgId);
        List<JobDO> jobDOS = jobMapper.selectByExample(jobExample);
        if (CollectionUtils.isEmpty(jobDOS)) {
            return new ArrayList<>();
        } else {
            return jobDOS.stream().map(jobConverter::convertDOToModel).collect(Collectors.toList());
        }
    }

    @Override
    public Boolean delJob(DelJobReq req) {
        List<Job> subJobs = querySubJobsById(req.getJobId());
        if (!CollectionUtils.isEmpty(subJobs)) {
            throw new BusinessException(ErrorCode.code("JOB_HAS_SUB_JOBS"));
        }
        JobDO jobDO = new JobDO();
        jobDO.setId(req.getJobId());
        jobDO.setGmtModified(new Date());
        jobDO.setModifier(req.getOperator());
        jobDO.setIsDeleted("y");
        jobMapper.updateByPrimaryKeySelective(jobDO);
        return true;
    }

    @Override
    public List<Job> querySubJobsById(Long jobId) {
        JobExample jobExample = new JobExample();
        jobExample.createCriteria().andIsDeletedEqualTo("n").andSuperiorJobIdEqualTo(jobId);
        List<JobDO> jobDOS = jobMapper.selectByExample(jobExample);
        if (CollectionUtils.isEmpty(jobDOS)) {
            return new ArrayList<>();
        } else {
            return jobDOS.stream().map(jobConverter::convertDOToModel).collect(Collectors.toList());
        }
    }

    @Override
    public Map<UserJobRelationTypeEnum, List<Job>> queryUserJobs(Long userId) {
        UserJobRelationExample userJobRelationExample = new UserJobRelationExample();
        userJobRelationExample.createCriteria().andUserIdEqualTo(userId).andIsDeletedEqualTo("n");
        List<UserJobRelationDO> userJobRelationDOS = userJobRelationMapper.selectByExample(userJobRelationExample);
        if (CollectionUtils.isEmpty(userJobRelationDOS)) {
            return Maps.newHashMap();
        }
        Map<UserJobRelationTypeEnum, List<Job>> jobMap = new HashMap<>();
        userJobRelationDOS.forEach(relationDO -> {
            UserJobRelationTypeEnum relationType = UserJobRelationTypeEnum.getByCode(relationDO.getRelationType());
            List<Job> jobs = jobMap.computeIfAbsent(relationType, type -> new ArrayList<>());
            Job job = queryById(relationDO.getJobId());
            if (job != null) {
                jobs.add(job);
            }
        });
        return jobMap;
    }

    @Override
    public Boolean saveUserMultiJob(SaveUserMultiJobReq req) {
        List<UserJobRelationDO> newRelations = jobConverter.convertToUserJobRelationDO(req);
        Map<Long, UserJobRelationDO> newMultiJobMap = newRelations.stream().collect(Collectors.toMap(UserJobRelationDO::getJobId, relation -> relation));
        //删除多余的岗位关系，插入新增的岗位关系，更新已有的岗位关系
        UserJobRelationExample example = new UserJobRelationExample();
        example.createCriteria().andIsDeletedEqualTo("n").andUserIdEqualTo(req.getUserId()).
                andRelationTypeNotEqualTo(UserJobRelationTypeEnum.MAIN_JOB.getCode());
        List<UserJobRelationDO> existJobRelations = userJobRelationMapper.selectByExample(example);
        List<Long> existJobIds = existJobRelations.stream().map(UserJobRelationDO::getJobId).collect(Collectors.toList());
        //删除多余的岗位关系
        existJobRelations.stream().filter(relation -> !newMultiJobMap.containsKey(relation.getJobId()))
                .map(UserJobRelationDO::getId).forEach(userJobRelationMapper::deleteByPrimaryKey);
        //插入新增的岗位关系
        newRelations.stream().filter(relation -> !existJobIds.contains(relation.getJobId()))
                .forEach(userJobRelationMapper::insertSelective);
        //更新已有的岗位关系
        existJobRelations.stream().filter(relation -> newMultiJobMap.containsKey(relation.getJobId()))
                .forEach(relation -> {
                    UserJobRelationDO userJobRelationDO = newMultiJobMap.get(relation.getJobId());
                    userJobRelationDO.setId(relation.getId());
                    userJobRelationMapper.updateByPrimaryKey(userJobRelationDO);
                });
        return true;
    }
}
